App.require([], function () {
    function Quaternion(x, y, z, w) {
        this.x = x || 0;
        this.y = y || 0;
        this.z = z || 0;
        this.w = (w !== undefined) ? w : 1;
    }
    Quaternion.prototype = {
        constructor: Quaternion,
    
        set: function (x, y, z, w) {
            this.x = x;
            this.y = y;
            this.z = z;
            this.w = w;
            return this;
        },
    
        inverse: function () {
            return this.conjugate().normalize();
        },
    
        conjugate: function () {
            this.x *= - 1;
            this.y *= - 1;
            this.z *= - 1;
    
            return this;
        },
    
        dot: function (v) {
            return this.x * v.x + this.y * v.y + this.z * v.z + this.w * v.w;
        },
    
        lengthSq: function () {
            return this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w;
        },
    
        length: function () {
            return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w);
        },
    
        normalize: function () {
            var l = this.length();
    
            if (l === 0) {
                this.x = 0;
                this.y = 0;
                this.z = 0;
                this.w = 1;
            } else {
    
                l = 1 / l;
                this.x = this.x * l;
                this.y = this.y * l;
                this.z = this.z * l;
                this.w = this.w * l;
            }
    
            return this;
        },
    
        multiply: function (q) {
            return Quaternion.multiply(this, q, this);
        },  
    
        premultiply: function (q) {
            return Quaternion.multiply(q, this, this);
        },
    
        slerp: function (qb, t) {
            if (t === 0) return this;
            if (t === 1) return this.copy(qb);
    
            var x = this.x, y = this.y, z = this.z, w = this.w;
            var cosHalfTheta = w * qb.w + x * qb.x + y * qb.y + z * qb.z;
    
            if (cosHalfTheta < 0) {
                this.w = - qb.w;
                this.x = - qb.x;
                this.y = - qb.y;
                this.z = - qb.z;
                cosHalfTheta = - cosHalfTheta;
            } else {
                this.copy(qb);
            }
    
            if (cosHalfTheta >= 1.0) {
                this.w = w;
                this.x = x;
                this.y = y;
                this.z = z;
                return this;
    
            }
    
            var sinHalfTheta = Math.sqrt(1.0 - cosHalfTheta * cosHalfTheta);
    
            if (Math.abs(sinHalfTheta) < 0.001) {
                this.w = 0.5 * (w + this.w);
                this.x = 0.5 * (x + this.x);
                this.y = 0.5 * (y + this.y);
                this.z = 0.5 * (z + this.z);
                return this;
    
            }
    
            var halfTheta = Math.atan2(sinHalfTheta, cosHalfTheta);
            var ratioA = Math.sin((1 - t) * halfTheta) / sinHalfTheta,
                ratioB = Math.sin(t * halfTheta) / sinHalfTheta;
    
            this.w = (w * ratioA + this.w * ratioB);
            this.x = (x * ratioA + this.x * ratioB);
            this.y = (y * ratioA + this.y * ratioB);
            this.z = (z * ratioA + this.z * ratioB);
    
            return this;
        },
    
        equals: function (q) {
            return (q.x === this.x) && (q.y === this.y) && (q.z === this.z) && (q.w === this.w);
        },
    
        toArray: function (array, offset) {
            if (array === undefined) array = [];
            if (offset === undefined) offset = 0;
            array[offset] = this.x;
            array[offset + 1] = this.y;
            array[offset + 2] = this.z;
            array[offset + 3] = this.w;
    
            return array;
        }
    };
    
    Quaternion.slerpFlat = function (dst, dstOffset, src0, srcOffset0, src1, srcOffset1, t) {
        var x0 = src0[srcOffset0 + 0],
            y0 = src0[srcOffset0 + 1],
            z0 = src0[srcOffset0 + 2],
            w0 = src0[srcOffset0 + 3],
    
            x1 = src1[srcOffset1 + 0],
            y1 = src1[srcOffset1 + 1],
            z1 = src1[srcOffset1 + 2],
            w1 = src1[srcOffset1 + 3];
    
        if (w0 !== w1 || x0 !== x1 || y0 !== y1 || z0 !== z1) {
            var s = 1 - t,
                cos = x0 * x1 + y0 * y1 + z0 * z1 + w0 * w1,
                dir = (cos >= 0 ? 1 : - 1),
                sqrSin = 1 - cos * cos;
            if (sqrSin > 0.00001) {
                var sin = Math.sqrt(sqrSin),
                    len = Math.atan2(sin, cos * dir);
                s = Math.sin(s * len) / sin;
                t = Math.sin(t * len) / sin;
            }
    
            var tDir = t * dir;
            x0 = x0 * s + x1 * tDir;
            y0 = y0 * s + y1 * tDir;
            z0 = z0 * s + z1 * tDir;
            w0 = w0 * s + w1 * tDir;
            if (s === 1 - t) {
                var f = 1 / Math.sqrt(x0 * x0 + y0 * y0 + z0 * z0 + w0 * w0);
                x0 *= f;
                y0 *= f;
                z0 *= f;
                w0 *= f;
    
            }
        }
    
        dst[dstOffset] = x0;
        dst[dstOffset + 1] = y0;
        dst[dstOffset + 2] = z0;
        dst[dstOffset + 3] = w0;
    };
    
    Quaternion.slerp = function (qa, qb, t, qm) {
        qm = qm || new Quaternion();
        return qm.copy(qa).slerp(qb, t);
    };
    
    Quaternion.fromEuler = function (euler, q) {
        q = q || new Quaternion();
    
        var x = euler.x, y = euler.y, z = euler.z, order = euler.order;
        var cos = Math.cos;
        var sin = Math.sin;
    
        var c1 = cos(x / 2);
        var c2 = cos(y / 2);
        var c3 = cos(z / 2);
    
        var s1 = sin(x / 2);
        var s2 = sin(y / 2);
        var s3 = sin(z / 2);
    
        if (order === 'XYZ') {
            q.x = s1 * c2 * c3 + c1 * s2 * s3;
            q.y = c1 * s2 * c3 - s1 * c2 * s3;
            q.z = c1 * c2 * s3 + s1 * s2 * c3;
            q.w = c1 * c2 * c3 - s1 * s2 * s3;
        } else if (order === 'YXZ') {
            q.x = s1 * c2 * c3 + c1 * s2 * s3;
            q.y = c1 * s2 * c3 - s1 * c2 * s3;
            q.z = c1 * c2 * s3 - s1 * s2 * c3;
            q.w = c1 * c2 * c3 + s1 * s2 * s3;
        } else if (order === 'ZXY') {
            q.x = s1 * c2 * c3 - c1 * s2 * s3;
            q.y = c1 * s2 * c3 + s1 * c2 * s3;
            q.z = c1 * c2 * s3 + s1 * s2 * c3;
            q.w = c1 * c2 * c3 - s1 * s2 * s3;
        } else if (order === 'ZYX') {
            q.x = s1 * c2 * c3 - c1 * s2 * s3;
            q.y = c1 * s2 * c3 + s1 * c2 * s3;
            q.z = c1 * c2 * s3 - s1 * s2 * c3;
            q.w = c1 * c2 * c3 + s1 * s2 * s3;
        } else if (order === 'YZX') {
            q.x = s1 * c2 * c3 + c1 * s2 * s3;
            q.y = c1 * s2 * c3 + s1 * c2 * s3;
            q.z = c1 * c2 * s3 - s1 * s2 * c3;
            q.w = c1 * c2 * c3 - s1 * s2 * s3;
        } else if (order === 'XZY') {
            q.x = s1 * c2 * c3 - c1 * s2 * s3;
            q.y = c1 * s2 * c3 - s1 * c2 * s3;
            q.z = c1 * c2 * s3 + s1 * s2 * c3;
            q.w = c1 * c2 * c3 + s1 * s2 * s3;
        }
        return q;
    };
    
    Quaternion.fromAxisAngle = function (axis, angle, q) {
        q = q || new Quaternion();
    
        var len = Math.sqrt(axis.x * axis.x + axis.y * axis.y + axis.z * axis.z);
    
        var halfAngle = angle / 2, s = Math.sin(halfAngle);
        q.x = axis.x / len * s;
        q.y = axis.y / len * s;
        q.z = axis.z / len * s;
        q.w = Math.cos(halfAngle);
    
        return q;
    };
    
    Quaternion.fromRotationMatrix = function (m, q) {
        q = q || new Quaternion();
    
        var te = m.elements,
            m11 = te[0], m12 = te[4], m13 = te[8],
            m21 = te[1], m22 = te[5], m23 = te[9],
            m31 = te[2], m32 = te[6], m33 = te[10],
            trace = m11 + m22 + m33,
            s;
        if (trace > 0) {
            s = 0.5 / Math.sqrt(trace + 1.0);
            q.w = 0.25 / s;
            q.x = (m32 - m23) * s;
            q.y = (m13 - m31) * s;
            q.z = (m21 - m12) * s;
        } 
        else if (m11 > m22 && m11 > m33) {
            s = 2.0 * Math.sqrt(1.0 + m11 - m22 - m33);
            q.w = (m32 - m23) / s;
            q.x = 0.25 * s;
            q.y = (m12 + m21) / s;
            q.z = (m13 + m31) / s;
        } 
        else if (m22 > m33) {
            s = 2.0 * Math.sqrt(1.0 + m22 - m11 - m33);
            q.w = (m13 - m31) / s;
            q.x = (m12 + m21) / s;
            q.y = 0.25 * s;
            q.z = (m23 + m32) / s;
        } 
        else {
            s = 2.0 * Math.sqrt(1.0 + m33 - m11 - m22);
            q.w = (m21 - m12) / s;
            q.x = (m13 + m31) / s;
            q.y = (m23 + m32) / s;
            q.z = 0.25 * s;
        }
    
        return q;
    };
    
    Quaternion.fromUnitVector3 = function (vFrom, vTo, q) {
        q = q || new Quaternion();
    
        var v1 = Quaternion.vector = Quaternion.vector || new Vector3();
        var r, EPS = 0.000001;
    
        r = vFrom.dot(vTo) + 1;
        if (r < EPS) {
            r = 0;
            if (Math.abs(vFrom.x) > Math.abs(vFrom.z)) {
                v1.set(- vFrom.y, vFrom.x, 0);
            } else {
                v1.set(0, - vFrom.z, vFrom.y);
            }
    
        } else {
            v1.crossVectors(vFrom, vTo);
        }
    
        q.x = v1.x;
        q.y = v1.y;
        q.z = v1.z;
        q.w = r;
    
        return q.normalize();
    };
    
    Quaternion.multiply = function (a, b, q) {
        q = q || new Quaternion();
    
        var qax = a.x, qay = a.y, qaz = a.z, qaw = a.w;
        var qbx = b.x, qby = b.y, qbz = b.z, qbw = b.w;
    
        q.x = qax * qbw + qaw * qbx + qay * qbz - qaz * qby;
        q.y = qay * qbw + qaw * qby + qaz * qbx - qax * qbz;
        q.z = qaz * qbw + qaw * qbz + qax * qby - qay * qbx;
        q.w = qaw * qbw - qax * qbx - qay * qby - qaz * qbz;
    
        return q;
    };
    
     Quaternion.fromArray = function (array, offset, q) {
         q = q || new Quaternion();
         if (offset === undefined) offset = 0;
        q.x = array[offset];
        q.y = array[offset + 1];
        q.z = array[offset + 2];
        q.w = array[offset + 3];
    
        return q;
     };

     App.define("quaternion", {
        Quaternion: Quaternion
     })
})